ElasticSearch Rest和(RPC)NodeClient

1 概述

ElasticSearch支持Restful和NodeClient两种通信方式。如下,TransportClient已经被Rest客户端代替,在8.0中会被移除。

@deprecated {@link TransportClient} is deprecated in favour of the High Level REST client and will be removed in Elasticsearch 8.0.

ES在启动类org.elasticsearch.bootstrap.Bootstrap中会实例化表示一个节点的org.elasticsearch.node.Node类,Node类的构造函数中会进行大量的模块、Service、Plugin等的初始化。

还有一点需要明确的是ES中的Rest和NodeClient都是基于Netty实现的。

2 Restful

2.1 Rest服务启动流程

ElasticSearch发送和接受报文的操作由org.elasticsearch.transport.TransportService类完成,通过跟踪其构造函数的调用栈可以发现在Node的构造函数中实例化了该类:


transportservice initial.png

在TransportService的构造函数中有一个参数Transport,该类是底层通信的关键。
该类在Node中实例化并作为参数传入TransportService的构造函数,Node中实例化代码如下:

final Transport transport = networkModule.getTransportSupplier().get();

继续跟踪到NetworkModule的代码中可以发现getTransportSupplier()返回的是Netty4Plugin,Netty4Pluign.get则返回如下:

() -> new Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher)

Node、TransportService以及Transport都实现了AbstractLifecycleComponent类,具有start、stop等生命周期方法,BootStrap在Node初始化完成后,会调用其start方法,Node也在其start方法中调用了Module、Service等的start方法。TransportService.doStart方法如下:

@Override
    protected void doStart() {
        transport.addMessageListener(this);
        connectionManager.addListener(this);
        transport.start();//调用transport的start方法
        if (transport.boundAddress() != null && logger.isInfoEnabled()) {
            logger.info("{}", transport.boundAddress());
            for (Map.Entry<String, BoundTransportAddress> entry : transport.profileBoundAddresses().entrySet()) {
                logger.info("profile [{}]: {}", entry.getKey(), entry.getValue());
            }
        }
        localNode = localNodeFactory.apply(transport.boundAddress());

        if (connectToRemoteCluster) {
            // here we start to connect to the remote clusters
            remoteClusterService.initializeRemoteClusters();
        }
    }

我们直接看Netty4HttpServerTransport的doStart方法:

@Override
    protected void doStart() {
        boolean success = false;
        try {
            serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(new NioEventLoopGroup(workerCount, daemonThreadFactory(settings,
                HTTP_SERVER_WORKER_THREAD_NAME_PREFIX)));
            serverBootstrap.channel(NioServerSocketChannel.class);
            //下面的一行配置了有客户端连接时的ChannelInitializer,ChannelInitializer主要用来配置处理客户端报文的Pipeline,下一小节会详述。
            serverBootstrap.childHandler(configureServerChannelHandler());
            serverBootstrap.handler(new ServerChannelExceptionHandler(this));

            serverBootstrap.childOption(ChannelOption.TCP_NODELAY, SETTING_HTTP_TCP_NO_DELAY.get(settings));
            serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, SETTING_HTTP_TCP_KEEP_ALIVE.get(settings));

            final ByteSizeValue tcpSendBufferSize = SETTING_HTTP_TCP_SEND_BUFFER_SIZE.get(settings);
            if (tcpSendBufferSize.getBytes() > 0) {
                serverBootstrap.childOption(ChannelOption.SO_SNDBUF, Math.toIntExact(tcpSendBufferSize.getBytes()));
            }

            final ByteSizeValue tcpReceiveBufferSize = SETTING_HTTP_TCP_RECEIVE_BUFFER_SIZE.get(settings);
            if (tcpReceiveBufferSize.getBytes() > 0) {
                serverBootstrap.childOption(ChannelOption.SO_RCVBUF, Math.toIntExact(tcpReceiveBufferSize.getBytes()));
            }

            serverBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, recvByteBufAllocator);
            serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, recvByteBufAllocator);

            final boolean reuseAddress = SETTING_HTTP_TCP_REUSE_ADDRESS.get(settings);
            serverBootstrap.option(ChannelOption.SO_REUSEADDR, reuseAddress);
            serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, reuseAddress);

            bindServer();
            success = true;
        } finally {
            if (success == false) {
                doStop(); // otherwise we leak threads since we never moved to started
            }
        }
    }

如上,其实就是Netty服务端的启动方法,配置Handler,端口号绑定等。

2.2 Rest请求处理

上面的代码注释中,特别提到了configureServerChannelHandler()函数,该函数代码如下:

public ChannelHandler configureServerChannelHandler() {
      //将自己(Transport)作为参数传入构造函数
        return new HttpChannelHandler(this, handlingSettings);
    }

protected static class HttpChannelHandler extends ChannelInitializer<Channel> {

        private final Netty4HttpServerTransport transport;
        private final Netty4HttpRequestHandler requestHandler;
        private final HttpHandlingSettings handlingSettings;

        protected HttpChannelHandler(final Netty4HttpServerTransport transport, final HttpHandlingSettings handlingSettings) {
            this.transport = transport;
            this.handlingSettings = handlingSettings;
            this.requestHandler = new Netty4HttpRequestHandler(transport);
        }

        @Override
        protected void initChannel(Channel ch) throws Exception {
            Netty4HttpChannel nettyHttpChannel = new Netty4HttpChannel(ch);
            ch.attr(HTTP_CHANNEL_KEY).set(nettyHttpChannel);
            ch.pipeline().addLast("read_timeout", new ReadTimeoutHandler(transport.readTimeoutMillis, TimeUnit.MILLISECONDS));
            final HttpRequestDecoder decoder = new HttpRequestDecoder(
                handlingSettings.getMaxInitialLineLength(),
                handlingSettings.getMaxHeaderSize(),
                handlingSettings.getMaxChunkSize());
            decoder.setCumulator(ByteToMessageDecoder.COMPOSITE_CUMULATOR);
            //编码器、压缩器、解码器的设置
            ch.pipeline().addLast("decoder", decoder);
            ch.pipeline().addLast("decoder_compress", new HttpContentDecompressor());
            ch.pipeline().addLast("encoder", new HttpResponseEncoder());
            final HttpObjectAggregator aggregator = new HttpObjectAggregator(handlingSettings.getMaxContentLength());
            aggregator.setMaxCumulationBufferComponents(transport.maxCompositeBufferComponents);
            ch.pipeline().addLast("aggregator", aggregator);
            if (handlingSettings.isCompression()) {
                ch.pipeline().addLast("encoder_compress", new HttpContentCompressor(handlingSettings.getCompressionLevel()));
            }
            if (handlingSettings.isCorsEnabled()) {
                ch.pipeline().addLast("cors", new Netty4CorsHandler(transport.corsConfig));
            }
            ch.pipeline().addLast("pipelining", new Netty4HttpPipeliningHandler(transport.logger, transport.pipeliningMaxEvents));
            //这里是注册对请求Request进行处理的handler,requestHandler在其构造函数中被初始化为Netty4HttpRequestHandler(transport)
            ch.pipeline().addLast("handler", requestHandler);
            transport.serverAcceptedChannel(nettyHttpChannel);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ExceptionsHelper.maybeDieOnAnotherThread(cause);
            super.exceptionCaught(ctx, cause);
        }
    }

Rest服务端(也就是处理客户端请求的节点),收到报文处理的逻辑主要在上面的Netty4HttpRequestHandler(transport)中,该类实现了netty的inboundhandler

class Netty4HttpRequestHandler extends SimpleChannelInboundHandler<HttpPipelinedRequest<FullHttpRequest>> 

实现的函数channelRead0用来处理请求报文,其中关键代码为:

serverTransport.incomingRequest(httpRequest, channel);

serverTransport就是我们通过构造传入的Netty4HttpServerTransport实例,其继承关系如下图:


Netty4HttpServerTransport class graph.png

Netty4HttpRequestHandler.channelRead0->
AbstractHttpServerTransport.incomingRequest->
handleIncomingRequest->dispatchRequest
最后dispatchRequest的实现如下:

 // Visible for testing
    void dispatchRequest(final RestRequest restRequest, final RestChannel channel, final Throwable badRequestCause) {
        final ThreadContext threadContext = threadPool.getThreadContext();
        try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
            if (badRequestCause != null) {
                dispatcher.dispatchBadRequest(restRequest, channel, threadContext, badRequestCause);
            } else {
                dispatcher.dispatchRequest(restRequest, channel, threadContext);
            }
        }
    }

通过查看方法的调用轨迹可以知道dispatcher为RestController实例,该类类似于SpringMVC的Controller,负责(method,path)->RestHandler的映射,最后会根据method(指Http方法,GET、POST、DELETE、PUT、POST等)以及路径来找到注册的RestHandler,并使用此RestHandler来处理该请求。
首先看RestController.dispatchRequest方法定义:

@Override
    public void dispatchRequest(RestRequest request, RestChannel channel, ThreadContext threadContext) {
        ...
            tryAllHandlers(request, channel, threadContext);
        ...
    }

void tryAllHandlers(final RestRequest request, final RestChannel channel, final ThreadContext threadContext) throws Exception {
        for (String key : headersToCopy) {
            String httpHeader = request.header(key);
            if (httpHeader != null) {
                threadContext.putHeader(key, httpHeader);
            }
        }
        // Request execution flag
        boolean requestHandled = false;

        if (checkErrorTraceParameter(request, channel) == false) {
            channel.sendResponse(
                    BytesRestResponse.createSimpleErrorResponse(channel, BAD_REQUEST, "error traces in responses are disabled."));
            return;
        }

        // Loop through all possible handlers, attempting to dispatch the request
        //依次迭代所有匹配上方法、路径的Handler
        Iterator<MethodHandlers> allHandlers = getAllHandlers(request);
        for (Iterator<MethodHandlers> it = allHandlers; it.hasNext(); ) {
            final Optional<RestHandler> mHandler = Optional.ofNullable(it.next()).flatMap(mh -> mh.getHandler(request.method()));
            requestHandled = dispatchRequest(request, channel, client, mHandler);
            if (requestHandled) {
                break;
            }
        }

        // If request has not been handled, fallback to a bad request error.
        if (requestHandled == false) {
            handleBadRequest(request, channel);
        }
    }

boolean dispatchRequest(final RestRequest request, final RestChannel channel, final NodeClient client,
                            final Optional<RestHandler> mHandler) throws Exception {
...
//对于找到的Handler,调用其handleRequest方法
                wrappedHandler.handleRequest(request, responseChannel, client);
                ...
    }

下面说一下RestController中handler的注册过程,其实RestController实例化调用如下:
Node构造函数中实例化ActionModule
->ActionModule构造函数中会new RestController
最后Node会调用ActionModule的actionModule.initRestHandlers方法在RestController中注册handler,所有的handler子类都会在自己的构造函数中向controller中注册自己,比如:

public RestMainAction(Settings settings, RestController controller) {
        super(settings);
        controller.registerHandler(GET, "/", this);
        controller.registerHandler(HEAD, "/", this);
    }

controller其实是使用Trie树(字典树)维护handler的。
上面接受到请求然后找到合适的handler,最后会调用handler的handleRequest方法,该方法在其父类BaseRestHandler中定义:

@Override
    public final void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
        // prepare the request for execution; has the side effect of touching the request parameters
      //prepareRequest在具体的子类中实现,具体的为根据不同的子类,返回不同的Action,如下面RestSearchAction的实现
        final RestChannelConsumer action = prepareRequest(request, client);

        // validate unconsumed params, but we must exclude params used to format the response
        // use a sorted set so the unconsumed parameters appear in a reliable sorted order
        final SortedSet<String> unconsumedParams =
            request.unconsumedParams().stream().filter(p -> !responseParams().contains(p)).collect(Collectors.toCollection(TreeSet::new));

        // validate the non-response params
        if (!unconsumedParams.isEmpty()) {
            final Set<String> candidateParams = new HashSet<>();
            candidateParams.addAll(request.consumedParams());
            candidateParams.addAll(responseParams());
            throw new IllegalArgumentException(unrecognized(request, unconsumedParams, candidateParams, "parameter"));
        }

        usageCount.increment();
        // execute the action
        action.accept(channel);
    }
//比如RestSearchAction的实现如下:
@Override
    public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
        SearchRequest searchRequest = new SearchRequest();
        /*
         * We have to pull out the call to `source().size(size)` because
         * _update_by_query and _delete_by_query uses this same parsing
         * path but sets a different variable when it sees the `size`
         * url parameter.
         *
         * Note that we can't use `searchRequest.source()::size` because
         * `searchRequest.source()` is null right now. We don't have to
         * guard against it being null in the IntConsumer because it can't
         * be null later. If that is confusing to you then you are in good
         * company.
         */
        IntConsumer setSize = size -> searchRequest.source().size(size);
        request.withContentOrSourceParamParserOrNull(parser ->
            parseSearchRequest(searchRequest, request, parser, setSize));

        return channel -> client.search(searchRequest, new RestStatusToXContentListener<>(channel));
    }

handleRequest最后会调用action.accept,最终会调用client.search(),通过跟踪调用轨迹可以,cient为RestController中的成员变量client,是通过Node实例化ActionModule传入的NodeClient对象。

可以看下NodeClient的search方法,在其父类AbstractClient定义如下:

@Override
    public void search(final SearchRequest request, final ActionListener<SearchResponse> listener) {
        execute(SearchAction.INSTANCE, request, listener);
    }

好了,其实通过跟踪可以知道,最终会调用NodeClient的方法如下:

public <    Request extends ActionRequest,
                Response extends ActionResponse
            > Task executeLocally(Action<Response> action, Request request, ActionListener<Response> listener) {
        return transportAction(action).execute(request, listener);
    }

上面代码中的SearchAction.INSTANCE其实是一个key,NodeClient同样会在ActionModule中注册key对应的TransportAction,具体可以看ActionModule.setupActions方法,这里不再赘述。
TransportAction各子类则负责具体的get, index, search, delete, reroute等的处理。

3 RPC NodeClient

其实分析到这里已经结束了,NodeClient的search, get, delete等函数都是通过key找到TransportAction的子类进行处理。

4 注意

注意ElasticSearch Rest/RPC 接口解析中提到的Transport*Action两层映射关系

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容